{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"https://habrastorage.org/web/677/8e1/337/6778e1337c3d4b159d7e99df94227cb2.jpg\"/>\n",
    "## Специализация \"Машинное обучение и анализ данных\"\n",
    "<center>Автор материала: программист-исследователь Mail.Ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ [Юрий Кашницкий](https://yorko.github.io/)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center> Capstone проект №1 <br> Идентификация пользователей по посещенным веб-страницам\n",
    "<img src='http://i.istockimg.com/file_thumbview_approve/21546327/5/stock-illustration-21546327-identification-de-l-utilisateur.jpg'>\n",
    "\n",
    "# <center>Неделя 6.  Vowpal Wabbit\n",
    "\n",
    "На этой неделе мы познакомимся с популярной библиотекой Vowpal Wabbit и попробуем ее на данных по посещению сайтов.\n",
    "\n",
    "**План 6 недели:**\n",
    "- Часть 1. Статья по Vowpal Wabbit\n",
    "- Часть 2. Применение Vowpal Wabbit к данным по посещению сайтов\n",
    " - 2.1. Подготовка данных\n",
    " - 2.2. Валидация по отложенной выборке\n",
    " - 2.3. Валидация по тестовой выборке (Public Leaderboard)\n",
    "\n",
    "**В этой части проекта Вам могут быть полезны видеозаписи следующих лекций курса \"Обучение на размеченных данных\":**\n",
    "   - [Стохатический градиентный спуск](https://www.coursera.org/learn/supervised-learning/lecture/xRY50/stokhastichieskii-ghradiientnyi-spusk)\n",
    "   - [Линейные модели. `sklearn.linear_model`. Классификация](https://www.coursera.org/learn/supervised-learning/lecture/EBg9t/linieinyie-modieli-sklearn-linear-model-klassifikatsiia)\n",
    "   \n",
    "Также будет полезна [презентация](https://github.com/esokolov/ml-course-msu/blob/master/ML15/lecture-notes/Sem08_vw.pdf) лектора специализации Евгения Соколова. И, конечно же, [документация](https://github.com/JohnLangford/vowpal_wabbit/wiki) Vowpal Wabbit."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание\n",
    "1. Заполните код в этой тетрадке \n",
    "2. Если вы проходите специализацию Яндеса и МФТИ, пошлите файл с ответами в соответствующем Programming Assignment. <br> Если вы проходите курс ODS, выберите ответы в [веб-форме](https://docs.google.com/forms/d/1wteunpEhAt_9s-WBwxYphB6XpniXsAZiFSNuFNmvOdk)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Часть 1. Статья про Vowpal Wabbit\n",
    "Прочитайте [статью](https://habrahabr.ru/company/ods/blog/326418/) про Vowpal Wabbit на Хабре из серии открытого курса OpenDataScience по машинному обучению. Материал для этой статьи зародился из нашей специализации. Скачайте [тетрадку](https://github.com/Yorko/mlcourse_open/blob/master/jupyter_russian/topic08_sgd_hashing_vowpal_wabbit/topic8_sgd_hashing_vowpal_wabbit.ipynb), прилагаемую к статье, посмотрите код, изучите его, поменяйте, только так можно разобраться с Vowpal Wabbit."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Часть 2. Применение Vowpal Wabbit к данным по посещению сайтов"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1. Подготовка данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Далее посмотрим на Vowpal Wabbit в деле. Правда, в задаче нашего соревнования при бинарной классификации веб-сессий мы разницы не заметим – как по качеству, так и по скорости работы (хотя можете проверить), продемонстрируем всю резвость VW в задаче классификации на 400 классов. Исходные данные все те же самые, но выделено 400 пользователей, и решается задача их идентификации. Скачайте данные [отсюда](https://inclass.kaggle.com/c/identify-me-if-you-can4/data) – файлы `train_sessions_400users.csv` и `test_sessions_400users.csv`.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import pandas as pd\n",
    "from scipy.sparse import csr_matrix\n",
    "from sklearn.linear_model import LogisticRegression, SGDClassifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Поменяйте на свой путь к данным\n",
    "PATH_TO_DATA = 'capstone_user_identification'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Загрузим обучающую и тестовую выборки. Можете заметить, что тестовые сессии здесь по времени четко отделены от сессий в обучающей выборке. **"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df_400 = pd.read_csv(os.path.join(PATH_TO_DATA,'train_sessions_400users.csv'), \n",
    "                           index_col='session_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_df_400 = pd.read_csv(os.path.join(PATH_TO_DATA,'test_sessions_400users.csv'), \n",
    "                           index_col='session_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df_400.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Видим, что в обучающей выборке 182793 сессий, в тестовой – 46473, и сессии действительно принадлежат 400 различным пользователям.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_df_400.shape, test_df_400.shape, train_df_400['user_id'].nunique()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Vowpal Wabbit любит, чтоб метки классов были распределены от 1 до K, где K – число классов в задаче классификации (в нашем случае – 400). Поэтому придется применить `LabelEncoder`, да еще и +1 потом добавить (`LabelEncoder` переводит метки в диапозон от 0 до K-1). Потом надо будет применить обратное преобразование.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "class_encoder = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "y_for_vw = ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Далее будем сравнивать VW с SGDClassifier и с логистической регрессией. Всем моделям этим нужна предобработка входных данных. Подготовьте для sklearn-моделей разреженные матрицы, как мы это делали в 5 части:**\n",
    "- объедините обучающиую и тестовую выборки\n",
    "- выберите только сайты (признаки от 'site1' до 'site10')\n",
    "- замените пропуски на нули (сайты у нас нумеровались с 0)\n",
    "- переведите в разреженный формат `csr_matrix`\n",
    "- разбейте обратно на обучающую и тестовую части"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sites = ['site' + str(i) for i in range(1, 11)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "''' ВАШ КОД ЗДЕСЬ '''\n",
    "X_train_sparse = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "X_test_sparse = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "y = ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2. Валидация по отложенной выборке"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Выделим обучающую (70%) и отложенную (30%) части исходной обучающей выборки. Данные не перемешиваем, учитываем, что сессии отсортированы по времени.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_share = int(.7 * train_df_400.shape[0])\n",
    "train_df_part = train_df_400[sites].iloc[:train_share, :]\n",
    "valid_df = train_df_400[sites].iloc[train_share:, :]\n",
    "X_train_part_sparse = X_train_sparse[:train_share, :]\n",
    "X_valid_sparse = X_train_sparse[train_share:, :]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_train_part = y[:train_share]\n",
    "y_valid = y[train_share:]\n",
    "y_train_part_for_vw = y_for_vw[:train_share]\n",
    "y_valid_for_vw = y_for_vw[train_share:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Реализуйте функцию, `arrays_to_vw`, переводящую обучающую выборку в формат Vowpal Wabbit.**\n",
    "\n",
    "Вход:\n",
    " - X – матрица `NumPy` (обучающая выборка)\n",
    " - y (необяз.) - вектор ответов (`NumPy`). Необязателен, поскольку тестовую матрицу будем обрабатывать этой же функцией\n",
    " - train – флаг, True в случае обучающей выборки, False – в случае тестовой выборки\n",
    " - out_file – путь к файлу .vw, в который будет произведена запись\n",
    " \n",
    "Детали:\n",
    "- надо пройтись по всем строкам матрицы `X` и записать через пробел все значения, предварительно добавив вперед нужную метку класса из вектора `y` и знак-разделитель `|`\n",
    "- в тестовой выборке на месте меток целевого класса можно писать произвольные, допустим, 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def arrays_to_vw(X, y=None, train=True, out_file='tmp.vw'):\n",
    "    ''' ВАШ КОД ЗДЕСЬ '''\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Примените написанную функцию к части обучащей выборки `(train_df_part, y_train_part_for_vw)`, к отложенной выборке `(valid_df, y_valid_for_vw)`, ко всей обучающей выборке и ко всей тестовой выборке. Обратите внимание, что на вход наш метод принимает именно матрицы и вектора `NumPy`.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# будет 4 вызова\n",
    "arrays_to_vw ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Результат должен получиться таким.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!head -3 $PATH_TO_DATA/train_part.vw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!head -3  $PATH_TO_DATA/valid.vw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!head -3 $PATH_TO_DATA/test.vw"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Обучите модель Vowpal Wabbitна выборке `train_part.vw`. Укажите, что решается задача классификации с 400 классами (`--oaa`), сделайте 3 прохода по выборке (`--passes`). Задайте некоторый кэш-файл (`--cache_file`, можно просто указать флаг `-c`), так VW будет быстрее делать все следующие после первого проходы по выборке (прошлый кэш-файл удаляется с помощью аргумента `-k`). Также укажите значение параметра `b`=26. Это число бит, используемых для хэширования, в данном случае нужно больше, чем 18 по умолчанию. Наконец, укажите `random_seed`=17. Остальные параметры пока не меняйте, далее уже в свободном режиме соревнования можете попробовать другие функции потерь.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_part_vw = os.path.join(PATH_TO_DATA, 'train_part.vw')\n",
    "valid_vw = os.path.join(PATH_TO_DATA, 'valid.vw')\n",
    "train_vw = os.path.join(PATH_TO_DATA, 'train.vw')\n",
    "test_vw = os.path.join(PATH_TO_DATA, 'test.vw')\n",
    "model = os.path.join(PATH_TO_DATA, 'vw_model.vw')\n",
    "pred = os.path.join(PATH_TO_DATA, 'vw_pred.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "!vw ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Запишите прогнозы на выборке *valid.vw* в *vw_valid_pred.csv*.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "!vw ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Считайте прогнозы *kaggle_data/vw_valid_pred.csv*  из файла и посмотрите на долю правильных ответов на отложенной части.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Теперь обучите `SGDClassifier` (3 прохода по выборке, логистическая функция потерь) и `LogisticRegression` на 70% разреженной обучающей выборки – `(X_train_part_sparse, y_train_part)`, сделайте прогноз для отложенной выборки `(X_valid_sparse, y_valid)` и посчитайте доли верных ответов. Логистическая регрессия будет обучаться не быстро (у меня – 4 минуты) – это нормально. Укажите везде `random_state`=17, `n_jobs`=-1. Для `SGDClassifier` также укажите `max_iter=3`.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logit = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "sgd_logit = ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "logit.fit ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "sgd_logit.fit ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**<font color='red'>Вопрос 1. </font> Посчитайте долю правильных ответов на отложенной выборке для Vowpal Wabbit, округлите до 3 знаков после запятой.**\n",
    "\n",
    "**<font color='red'>Вопрос 2. </font> Посчитайте долю правильных ответов на отложенной выборке для SGD, округлите до 3 знаков после запятой.**\n",
    "\n",
    "**<font color='red'>Вопрос 3. </font> Посчитайте долю правильных ответов на отложенной выборке для логистической регрессии, округлите до 3 знаков после запятой.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vw_valid_acc = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "sgd_valid_acc = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "logit_valid_acc = ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def write_answer_to_file(answer, file_address):\n",
    "    with open(file_address, 'w') as out_f:\n",
    "        out_f.write(str(answer))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "write_answer_to_file(round(vw_valid_acc, 3), 'answer6_1.txt')\n",
    "write_answer_to_file(round(sgd_valid_acc, 3), 'answer6_2.txt')\n",
    "write_answer_to_file(round(logit_valid_acc, 3), 'answer6_3.txt')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3. Валидация по тестовой выборке (Public Leaderboard)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Обучите модель VW с теми же параметрами на всей обучающей выборке – *train.vw*.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "!vw ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Сделайте прогноз для тестовой выборки.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "!vw ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Запишите прогноз в файл, примените обратное преобразование меток (был LabelEncoder и потом +1 в меткам) и отправьте решение на Kaggle.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def write_to_submission_file(predicted_labels, out_file,\n",
    "                             target='user_id', index_label=\"session_id\"):\n",
    "    # turn predictions into data frame and save as csv file\n",
    "    predicted_df = pd.DataFrame(predicted_labels,\n",
    "                                index = np.arange(1, predicted_labels.shape[0] + 1),\n",
    "                                columns=[target])\n",
    "    predicted_df.to_csv(out_file, index_label=index_label)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vw_pred = ''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "write_to_submission_file(vw_pred, os.path.join(PATH_TO_DATA, 'vw_400_users.csv'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Сделайте то же самое для SGD и логистической регрессии. Тут уже ждать обучение логистической регрессии совсем скучно (заново запускать тетрадку вам не захочется), но давайте дождемся.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "''' ВАШ КОД ЗДЕСЬ '''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "write_to_submission_file(sgd_logit_test_pred, \n",
    "                         os.path.join(PATH_TO_DATA, 'logit_400_users.csv'))\n",
    "write_to_submission_file(sgd_logit_test_pred, \n",
    "                         os.path.join(PATH_TO_DATA, 'sgd_400_users.csv'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на доли правильных ответов на публичной части (public leaderboard) тестовой выборки [этого](https://inclass.kaggle.com/c/identify-me-if-you-can4) соревнования.\n",
    "\n",
    "**<font color='red'>Вопрос 4. </font> Какова доля правильных ответов на публичной части тестовой выборки (public leaderboard)  для Vowpal Wabbit?**\n",
    "\n",
    "**<font color='red'>Вопрос 5. </font> Какова доля правильных ответов на публичной части тестовой выборки (public leaderboard)  для SGD?**\n",
    "\n",
    "**<font color='red'>Вопрос 6. </font> Какова доля правильных ответов на публичной части тестовой выборки (public leaderboard)  для логистической регрессии?**\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vw_lb_score, sgd_lb_score, logit_lb_score = ''' ВАШ КОД ЗДЕСЬ '''\n",
    "\n",
    "write_answer_to_file(round(vw_lb_score, 3), 'answer6_4.txt')\n",
    "write_answer_to_file(round(sgd_lb_score, 3), 'answer6_5.txt')\n",
    "write_answer_to_file(round(logit_lb_score, 3), 'answer6_6.txt')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**В заключение по заданию:**\n",
    "- Про соотношение качества классификации и скорости обучения VW, SGD и logit выводы предлагается сделать самостоятельно\n",
    "- Пожалуй, задача классификации на 400 классов (идентификация 400 пользователей) решается недостаточно хорошо при честном отделении по времени тестовой выборки от обучающей. Далее мы будем соревноваться в идентификации одного пользователя (Элис) – [вот](https://inclass.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking2) соревнование, в котором предлагается поучаствовать. Не перепутайте! \n",
    "\n",
    "**Удачи!**"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
